/* Copyright (c) 2008, RPI-ACM, Jesse Kempf <jessekempf@gmail.com> 
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * - Neither the name of RPI-ACM nor the names of its contributors may
 *   be used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <Python.h>
#include <sys/signal.h>
#include <signal.h>
#include <string.h>

struct unixutils_safesig {
	uint32_t counter;
	char in_use;
};

static struct unixutils_safesig unixutils_signal[NSIG];

static PyObject *unixutils_install_handler(PyObject *, PyObject *);
static PyObject *unixutils_remove_handler(PyObject *, PyObject *);
static PyObject *unixutils_get_sig_counter(PyObject *, PyObject *);
static PyObject *unixutils_set_sig_counter(PyObject *, PyObject *);
static PyObject *unixutils_clear_sig_counter(PyObject *, PyObject *);

PyDoc_STRVAR(safesig_doc, "PyNIX Safer Signals Module.\n"
"\n"
"Non-reentrant signal handlers can be used to corrupt the heap.\n"
"Ultimately the only reentrant signal handlers are those that set\n"
"a flag and return control. It is not entirely clear what the Python\n"
"interpreter will do when calling a function object, and so it is not\n"
"clear whether any signal handlers written in python are safe.\n"
"\n"
"The safesig extension module solves this problem by providing a\n"
"simple mechanism for registering flag-setting signal handlers and a\n"
"way of checking the flags.\n"
"\n"
"All functions with the exception of install_handler() raise ValueError\n"
"if called for a signal not being handled by safesig.");

static PyOS_sighandler_t sig_handler(int sig) {
	unixutils_signal[sig].counter++;
	PyOS_setsig(sig, sig_handler);
}

static int unixutils_valid_signal(int sig) {
	if (sig < 1 || sig > NSIG) {
		PyErr_SetString(PyExc_ValueError, "Invalid signal.");
		return (0);
	}

	if (unixutils_signal[sig].in_use == 0) {
		PyErr_SetString(PyExc_ValueError, 
		"Signal not being handled by safesig.");
		return (0);
	}

	return (1);
}


PyDoc_STRVAR(install_handler_doc, "install_handler(signo)\n"
"\n"
"Install a safe signal handler for the given signal number.\n"
"\n"
"Each time signal signo is received, the handler will increment\n"
"the appropriate flag. The flag's value can be retrieved with\n"
"get_sig_counter() and manipulated with clear_sig_counter() and\n"
"set_sig_counter().");

static PyObject *unixutils_install_handler(PyObject *self, PyObject *args) {
	int sig;

	if (!PyArg_ParseTuple(args, "i", &sig))
		return (NULL);

	if (PyOS_setsig(sig, sig_handler) == SIG_ERR) {
		PyErr_SetFromErrno(PyExc_OSError);
		return (NULL);
	}

	unixutils_signal[sig].in_use = 1;

	Py_RETURN_NONE;
}

PyDoc_STRVAR(remove_handler_doc, "remove_handler(signo)\n"
"\n"
"Remove the safe signal handler for the given signal number.");

static PyObject *unixutils_remove_handler(PyObject *self, PyObject *args) {
	int sig;

	if (!PyArg_ParseTuple(args, "i", &sig))
		return (NULL);


	if (unixutils_valid_signal(sig)) {
		if (PyOS_setsig(sig, SIG_IGN) == SIG_ERR) {
			PyErr_SetFromErrno(PyExc_OSError);
			return (NULL);
		}

		unixutils_signal[sig].in_use = 0;

	} else 
		return (NULL);

	Py_RETURN_NONE;
}

PyDoc_STRVAR(get_sig_counter_doc, "get_sig_counter(signo) -> int\n"
"\n"
"Retrieve the flag for the given signo.");

static PyObject *unixutils_get_sig_counter(PyObject *self, PyObject *args) {
	int sig;

	if (!PyArg_ParseTuple(args, "i", &sig))
		return (NULL);

	if (unixutils_valid_signal(sig))
		return (Py_BuildValue("i", unixutils_signal[sig].counter));
	else
		return (NULL);
}

PyDoc_STRVAR(set_sig_counter_doc, "set_sig_counter(signo, int)\n"
"\n"
"Set the counter associated with the given signal to the provided\n"
"value.");
static PyObject *unixutils_set_sig_counter(PyObject *self, PyObject *args) {
	int sig, value;

	if (!PyArg_ParseTuple(args, "ii", &sig, &value))
		return (NULL);

	if (unixutils_valid_signal(sig))
		unixutils_signal[sig].counter = value;
	else
		return (NULL);

	Py_RETURN_NONE;	
}

PyDoc_STRVAR(clear_sig_counter_doc, "clear_sig_counter(signo)\n"
"\n"
"Clear the counter associated with the given signal.");
static PyObject *unixutils_clear_sig_counter(PyObject *self, PyObject *args) {
	int sig;

	if (!PyArg_ParseTuple(args, "i", &sig))
		return (NULL);

	if (unixutils_valid_signal(sig))
		unixutils_signal[sig].counter = 0;
	else
		return (NULL);

	Py_RETURN_NONE;
}

static PyMethodDef safesigMethods[] = {
	{"install_handler", unixutils_install_handler, METH_VARARGS,
		install_handler_doc},
	{"remove_handler", unixutils_remove_handler, METH_VARARGS,
		remove_handler_doc},
	{"get_sig_counter", unixutils_get_sig_counter, METH_VARARGS,
		get_sig_counter_doc},
	{"set_sig_counter", unixutils_set_sig_counter, METH_VARARGS,
		set_sig_counter_doc},
	{"clear_sig_counter", unixutils_clear_sig_counter, METH_VARARGS,
		clear_sig_counter_doc},
	{NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC initsafesig(void) {
	Py_InitModule3("safesig", safesigMethods, safesig_doc);
	memset(unixutils_signal, sizeof(struct unixutils_safesig) * NSIG, 0);
}
